﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Layers;
using System.Threading;
using System.Collections;

namespace Taci
{
    public partial class Taci : UserControl
    {
        // Graphic buffer
        InteractiveBuffer buffers;
        Rectangle rect;
        SolidBrush blackBrush;
        SolidBrush blueBrush;

        float columnWidth = -1F;
        float columnHeight = -1F;

        Pen blackPen;

        Bitmap[] numberImages;

        /* Threading */
        Thread thread;
        int sleepTime;

        bool step = false;
        bool stop = false;
        System.Windows.Forms.Timer refreshTimer;

        bool isCompleted = false;

        /* Algorithm */
        static Chess chess;

        public Taci()
        {
            InitializeComponent();

            refreshTimer = new System.Windows.Forms.Timer();
            refreshTimer.Interval = 600;
            refreshTimer.Tick += new EventHandler(refreshTimer_Tick);
        }

        #region Initialize
        private void InitValue()
        {
            if(chess == null)
                chess = new Chess();
        }
        #endregion

        #region Override Event
        protected override void OnLoad(EventArgs e)
        {
            // Init algorithm value
            if (this.columnWidth == -1F)
            {
                this.columnWidth = this.Width / 3;
                this.columnHeight = this.Height / 3;
            }
            this.InitValue();

            // Control style
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
            SetStyle(ControlStyles.Opaque, true);
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;

            this.blackBrush = new SolidBrush(Color.Black);
            this.blueBrush = new SolidBrush(Color.Blue);
            this.blackPen = new Pen(this.blackBrush, 1.0F);

            this.rect = new Rectangle(0, 0, this.Width, this.Height);
            this.buffers = new InteractiveBuffer(this, rect);
            this.buffers.AddLayer("background");
            this.buffers.AddLayer("top");
            this.buffers["background"].Func += new RepresetBuffer.DrawBufferHandle(DrawBackGround);
            this.buffers["top"].Func += new RepresetBuffer.DrawBufferHandle(DrawNumbers);

            this.sleepTime = 1000; // Default sleep time

            // Init image
            if (this.numberImages == null)
            {
                this.numberImages = new Bitmap[9];
                string filePath = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
                filePath += "\\imgs\\nums\\";
                string imgPath = "";
                for (int i = 0; i < 9; i++)
                {
                    imgPath = filePath + i.ToString() + ".png";
                    try
                    {
                        this.numberImages[i] = (Bitmap)Bitmap.FromFile(imgPath);
                    }
                    catch (Exception exc)
                    {
                        Console.WriteLine(exc.StackTrace);
                    }
                }
            }


        }
        #endregion

        #region Drawing
        private void DrawBackGround(Graphics g, Rectangle r)
        {
            g.Clear(Color.White);

            // Draw rows
            for (int i = 1; i < 3; i++)
            {
                Point p1 = new Point((int)(i * this.columnWidth)  , 0);
                Point p2 = new Point(p1.X , this.Height);
                g.DrawLine(this.blackPen, p1, p2);
            }

            // Draw columns
            for (int i = 1; i < 3; i++)
            {
                Point p1 = new Point(0, (int)(i * this.columnHeight));
                Point p2 = new Point(this.Width, p1.Y);
                g.DrawLine(this.blackPen, p1, p2);
            }

        }

        private void DrawNumbers(Graphics g, Rectangle r)
        {
            for (int k = 0; k < 9; k++)
            {
                int i = k / 3;
                int j = k % 3;
                int x = (int)(this.columnWidth * i);
                int y = (int)(this.columnHeight * j);
                int number = chess.data[j][i];
                Rectangle tmpRect = new Rectangle(x, y, (int)this.columnWidth, (int)this.columnHeight);
                g.DrawImage(numberImages[number], tmpRect);
            }
        }

        void refreshTimer_Tick(object sender, EventArgs e)
        {
            this.Refresh();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (this.IsCompleted)
            {
                this.buffers.Render(e.Graphics);
                return;
            }
            try
            {
                this.buffers.Render(e.Graphics);
            }
            catch (Exception exc)
            {
                MessageBox.Show(exc.ToString());
            }
        }

        protected override void OnResize(EventArgs e)
        {
            // set new value
            this.columnWidth = this.Width / 3;
            this.columnHeight = this.Height / 3;

            // Recreate buffer
            if (this.buffers != null)
            {
                this.rect = new Rectangle(0, 0, this.Width, this.Height);
                this.buffers = new InteractiveBuffer(this, rect);
                this.buffers.AddLayer("background");
                this.buffers.AddLayer("top");
                this.buffers["background"].Func += new RepresetBuffer.DrawBufferHandle(DrawBackGround);
                this.buffers["top"].Func += new RepresetBuffer.DrawBufferHandle(DrawNumbers);
                this.buffers.RefreshContext();
                this.Refresh();
            }


        }
        #endregion

        #region Popup

        public void ForceRefresh()
        {
            refreshTimer.Start();
        }

        #endregion

        #region Setup Condition
        public void SetStartState(string state)
        {
            chess.SetStartState(state);
            this.Refresh();
        }

        public void SetStopState(string state)
        {
            chess.SetStopState(state);
            this.Refresh();
        }
        #endregion

        #region Simualation
        public void StartSimulate()
        {
            if (thread == null)
            {
                this.isCompleted = false;
                thread = new Thread(new ThreadStart(this.Simulate));
                this.refreshTimer.Start();
                thread.Start();
            }
            else
            {
                this.stop = false;
            }
        }

        public void StopSimulate()
        {
            this.stop = true;
        }

        public void AbortSimulate()
        {
            if (thread != null)
            {
                thread.Abort();
            }
        }

        public void ResetSimulate()
        {
            if (thread != null)
            {
                this.isCompleted = false;
                thread.Abort();
                chess.ResetState();
                this.Refresh();
                this.refreshTimer.Stop();
                thread = null;
            }
            else
            {
                this.isCompleted = false;
                chess.ResetState();
                this.Refresh();
            }
        }

        public void Simulate()
        {
            while (chess.NextState())
            {
                if (this.step == true)
                {
                    this.step = false;
                    while (this.step == false)
                    {
                        // Stop thread
                        Thread.Sleep(100);
                    }
                }
                
                while (this.stop == true)
                {
                    Thread.Sleep(100);
                }
                if (this.sleepTime != 0)
                {
                    Thread.Sleep(this.sleepTime);
                }
            }
            this.isCompleted = true;
        }

        public void Step()
        {
            this.step = true;
        }
        #endregion

        #region Properties
        public bool IsCompleted
        {
            get
            {
                return this.isCompleted;
            }
        }

        /// <summary>
        /// Set delay for simulate process
        /// </summary>
        public int SleepTime
        {
            set
            {
                this.sleepTime = value;
            }
        }
        #endregion
    }

    class Chess
    {
        public int[][] data;
        public int[][] wishState;

        string startState = "0,1,2,3,4,5,6,7,8";
        string endState = "8,7,6,5,4,3,2,1,0";

        private int lastIndex = -1;

        public Chess()
        {
            this.SetStartState(startState);
            this.SetStopState(endState);
        }

        public void ResetState()
        {
            this.SetStartState(startState);
            this.SetStopState(endState);
        }

        private int[] ConvertStringToIntArr(string state)
        {
            var strArr = state.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
            if (strArr.Length != 9)
            {
                throw new Exception("Nhập vào dãy số sai.");
            }
            int[] result = new int[9];
            for (int i = 0; i < 9; i++)
            {
                result[i] = int.Parse(strArr[i]);
            }
            int[] realResult = result.Distinct().ToArray();
            if (realResult.Length != 9)
            {
                throw new Exception("Nhập vào dãy số sai");
            }
            int max = realResult.Max();
            int min = realResult.Min();
            if ( max> 8 || min < 0)
            {
                throw new Exception("Nhập vào dãy số sai");
            }
            return realResult;
        }

        public void SetStartState(string state)
        {
            int[] stateArr = new int[9];
            try
            {
               stateArr = ConvertStringToIntArr(state);
            }
            catch (Exception exc)
            {
                throw exc;
            }
            // Set state
            this.startState = state;

            data = new int[3][];
            int counter = 0;
            for (int i = 0; i < 3; i++)
            {
                data[i] = new int[3];
                for (int j = 0; j < 3; j++)
                {
                    data[i][j] = stateArr[counter];
                    counter++;
                }
            }
        }

        public void SetStopState(string state)
        {
            int[] stateArr = new int[9];
            try
            {
                stateArr = ConvertStringToIntArr(state);
            }
            catch (Exception exc)
            {
                throw exc;
            }
            // set state
            this.endState = state;

            wishState = new int[3][];
            int counter = 0;
            for (int i = 0; i < 3; i++)
            {
                wishState[i] = new int[3];
                for (int j = 0; j < 3; j++)
                {
                    wishState[i][j] = stateArr[counter];
                    counter++;
                }
            }
        }

        /// <summary>
        /// Compute and create next state of taci
        /// </summary>
        /// <returns>True if this the switched state is wish state</returns>
        public bool NextState()
        {
            /* Find next index and max heuristic value */
            int maxIndex = this.MaxIndex(this.data);

            /* Go to next state */
            int zeroIndex = this.ZeroIndex(this.data);
            lastIndex = zeroIndex; // remember previous state
            this.data[zeroIndex / 3][zeroIndex % 3] = this.data[maxIndex / 3][maxIndex % 3];
            this.data[maxIndex / 3][maxIndex % 3] = 0;

            return !this.IsWishState();
        }

        private bool IsWishState()
        {
            int zeroIndex = this.ZeroIndex(this.data);
            int matchs = this.HeuristicValue(zeroIndex, this.wishState);
            if (matchs == 9)
                return true;
            return false;
        }

        private int MaxIndex(int[][] _data)
        {
            int[] indexs = this.NextPos(_data);
            Dictionary<int, int> indexDic = new Dictionary<int, int>();
            for (int i = 0; i < indexs.Length; i++)
            {
                int tempH = this.HeuristicValue(indexs[i], _data);
                indexDic.Add(indexs[i], tempH);
            }
            indexDic.Remove(lastIndex);
            int maxIndex = -1;
            int maxH = -1;
            foreach (var item in indexDic)
            {
                if (maxH < item.Value)
                {
                    maxH = item.Value;
                    maxIndex = item.Key;
                }
            }

            // Copy to new dictionary
            int zeroIndex = this.ZeroIndex(_data);
            Dictionary<int, int> sameDic = new Dictionary<int, int>();
            foreach (var item in indexDic)
            {
                if (item.Value == maxH)
                {
                    sameDic.Add(item.Key, item.Value);
                }
            }
            // Get random value
            Random rand = new Random((DateTime.Now.Millisecond));
            int randIndex = rand.Next(0, sameDic.Count);
            List<KeyValuePair<int,int>> list = sameDic.ToList();
            maxIndex = list[randIndex].Key;
            

            // Get final value
            return maxIndex;
        }

        /// <summary>
        /// Find the zero index in taci chess
        /// </summary>
        /// <returns></returns>
        private int ZeroIndex(int[][] _data)
        {
            // find zero index
            for (int i = 0; i < 9; i++)
            {
                if (_data[i / 3][i % 3] == 0)
                {
                    return i;
                }
            }
            throw new Exception("Error in data table, zero index not found");
        }

        /// <summary>
        /// Find relate index with zero taci
        /// </summary>
        /// <returns></returns>
        private int[] NextPos(int[][] _data)
        {
            List<int> tempReuslt = new List<int>();
            int index = this.ZeroIndex(_data);
            int devide = index / 3;
            int odd = index % 3;
            
            int checker = index - 1;
            if (checker >= 0 && checker <= 8)
            {
                if(checker / 3 == devide)
                    tempReuslt.Add(checker);
            }
            checker = index + 1;
            if (checker >= 0 && checker <= 8)
            {
                if (checker / 3 == devide)
                    tempReuslt.Add(checker);
            }

            checker = index - 3;
            if (checker >= 0 && checker <= 8)
            {
                if (checker % 3 == odd)
                    tempReuslt.Add(checker);
            }
            checker = index + 3;
            if (checker >= 0 && checker <= 8)
            {
                if (checker % 3 == odd)
                    tempReuslt.Add(checker);
            }

            int[] result = tempReuslt.ToArray();

            return result;
        }

        /// <summary>
        /// Find heuristic value of a number
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public int HeuristicValue(int index, int[][] _data)
        {
            // Copy to temp array

            int[][] temp = new int[3][];
            for (int i = 0; i < 3; i++)
            {
                temp[i] = new int[3];
                for (int j = 0; j < 3; j++)
                {
                    temp[i][j] = data[i][j];
                }
            }

            // Create new state
            int zeroIndex = this.ZeroIndex(_data);
            temp[zeroIndex / 3][zeroIndex % 3] = temp[index / 3][index % 3];
            temp[index / 3][index % 3] = 0;

            // Compute heuristic value
            int h = 0;
            for (int i = 0; i < 9; i++)
            {
                if (temp[i / 3][i % 3] == wishState[i / 3][i % 3])
                {
                    h++;
                }
            }
            return h;
        }
    }
}
